﻿#pragma once

#include <xy/alg/projection.h>

namespace xy
{

namespace alg
{

struct distance_maximum
{
    double dist = 0;

    void accumulate(double d)
    {
        dist = std::max(dist, d);
    }

    double result() const
    {
        return dist;
    }
};

struct distance_average
{
    double dist = 0;
    std::size_t count = 0;

    void accumulate(double d)
    {
        dist += d;
        count++;
    }

    double result() const
    {
        return dist / count;
    }
};

struct distance_mean_square
{
    double dist = 0;
    std::size_t count = 0;

    void accumulate(double d)
    {
        dist += d * d;
        count++;
    }

    double result() const
    {
        return dist / count;
    }
};

template <std::size_t N> struct distance_metric
{
    using vec_t = xy::geom::vec<N, double>; // P

    /**
     * @brief 计算两个点集之间的 Hausdorff 距离
     *
     * @param[in] cloud1
     * @param[in] cloud2
     * @return double
     */
    static double distance(const std::vector<vec_t> &cloud1, const std::vector<vec_t> &cloud2)
    {
        double dist = 0;
        for (std::size_t i = 0; i < cloud1.size(); i++)
        {
            double d = std::numeric_limits<double>::max();
            for (std::size_t j = 0; j < cloud2.size(); j++)
                d = std::min(d, cloud1[i].distance(cloud2[j]));
            dist = std::max(dist, d);
        }
        return dist;
    }

    /**
     * @brief 计算采样点到曲线的 Hausdorff 距离
     *
     * @param[in] samples
     * @param[in] curve
     * @return double
     */
    template <class Metric = distance_maximum>
    static double distance(const std::vector<vec_t> &samples, std::shared_ptr<xy::geom::curve<N>> curve)
    {
        Metric m;
        for (std::size_t i = 0; i < samples.size(); i++)
        {
            double t = (curve->first_param() + curve->last_param()) / 2;
            t = projection_point<N>::orthogonal2(curve, samples[i], t, xy::math::numeric::half);
            t = projection_point<N>::nearest(curve, samples[i], t, xy::math::numeric::half);
            auto p = curve->value(t);
            m.accumulate(p.distance(samples[i]));
        }
        return m.result();
    }

    /**
     * @brief 计算两条曲线之间的 Hausdorff 距离
     *
     * @param[in] curve1
     * @param[in] curve2
     * @return double
     */
    static double distance(std::shared_ptr<xy::geom::curve<N>> curve1, std::shared_ptr<xy::geom::curve<N>> curve2)
    {
        static constexpr int n = 1000;

        auto [first_param1, last_param1] = curve1->bound();
        auto [first_param2, last_param2] = curve2->bound();
        auto step1 = (last_param1 - first_param1) / (n - 1);
        auto step2 = (last_param2 - first_param2) / (n - 1);
        std::vector<vec_t> points1, points2;
        for (std::size_t i = 0; i < n; i++)
        {
            auto p = curve1->d0(first_param1 + i * step1);
            points1.push_back(p);
        }

        for (std::size_t i = 0; i < n; i++)
        {
            auto p = curve2->d0(first_param2 + i * step2);
            points2.push_back(p);
        }

        return distance(points1, points2);
    }

    /**
     * @brief 计算采样点到曲面的 Hausdorff 距离
     *
     * @param[in] samples
     * @param[in] surface
     * @return double
     */
    template <class Metric = distance_maximum>
    static double distance(const std::vector<vec_t> &samples, std::shared_ptr<xy::geom::surface<N>> surface)
    {
        Metric m;
        for (std::size_t i = 0; i < samples.size(); i++)
        {
            auto [u1, u2] = surface->bound(0);
            auto [v1, v2] = surface->bound(1);
            double u = (u1 + u2) / 2, v = (v1 + v2) / 2;
            auto uv = projection_point<N>::torus(surface, samples[i], u, v, xy::math::numeric::half);
            auto p = surface->value(uv.x(), uv.y());
            m.accumulate(p.distance(samples[i]));
        }
        return m.result();
    }

    /**
     * @brief 计算采样点到曲面的 Hausdorff 距离
     *
     * @param[in] samples
     * @param[in] params
     * @param[in] surface
     * @return double
     */
    template <class Metric = distance_maximum>
    static double distance(const std::vector<vec_t> &samples, const std::vector<xy::geom::vec2> &params,
                           std::shared_ptr<xy::geom::surface<N>> surface)
    {
        Metric m;
        for (std::size_t i = 0; i < samples.size(); i++)
        {
            double u = params[i].x(), v = params[i].y();
            auto p = surface->value(u, v);
            m.accumulate(p.distance(samples[i]));
        }
        return m.result();
    }
};

} // namespace alg

} // namespace xy
